/*=============================================================================
#     FileName: event_handler.cpp
#         Desc: 
#       Author: Ace
#        Email: goace@acm.org
#     HomePage: http://www.goace.co.cc
#      Version: 0.0.1
#   LastChange: 2011-05-23 00:01:17
#      History:
=============================================================================*/

#include  <stdio.h>
#include  <iostream>
#include  <string.h>
#include  <string>
#include  <sys/types.h>
#include  <sys/socket.h>
#include  <netinet/in.h>
#include  <sys/stat.h>
#include  <dirent.h>
#include  <fcntl.h>
#include  <errno.h>
#include  <stdlib.h>
#include  <map>
#include  <arpa/inet.h>
#include  "gserver.h"
#include  "event_handler.h"
#include  "my_sock_lib.h"

using namespace ace;
using std::string;
using std::cout;
using std::endl;
using std::cerr;
using std::map;

extern map<string,ctl_sock_info_t *> *online_info;

/* ---------------------------------------------------------------*/
/**
 * 函数简介:  当检测到用户多点登录的时候，强制另一点的用户登录
 *			  下线，并且记录上一次登录的日志
 *
 * 参数: info 用户的信息结构指针
 * 
 * 返回值：0表示成功  －1表示失败
 */
/* ---------------------------------------------------------------*/
int force_offline(ctl_sock_info *info )
{
	
	int32_t msg = -1;
	if( write(info->notify_fd,(const void*)&msg,sizeof(msg)) < 0 )
	{
		perror("force_offline write");
		cout << info->usr_name << "\tforce offline " << endl;
		usr_exit(info);
		return -1;
	}

	cout << info->usr_name << "\tforce offline " << endl;
	usr_exit(info);
	return 0;
}

/* ---------------------------------------------------------------*/
/**
 * 函数简介: 从客户端读取路径   
 *
 * 参数: ptr 用户信息指针
 *
 * 返回值:   -1表示失败  0表示成功
 */
/* ---------------------------------------------------------------*/
int data_read_path(data_sock_info_t *ptr, int mode)
{
	char path[PATH_LEN];

	setblocking(ptr->sock_fd);
	int res = readn(ptr->sock_fd, path, PATH_LEN);
	setnonblocking(ptr->sock_fd);

	if(res == 0)		//客户关闭socket
	{
		close(ptr->sock_fd);
		delete ptr;
		return -1;
	}
	else if(res == -1)	//read错误
	{
		if( errno == EAGAIN || errno == EWOULDBLOCK )
		{
			ptr->state = WAIT_PATH;
			return -1;
		}
		
		data_error("data_read_path",ptr);
		
		return -1;
	}
	
	//根据不同的同步模式(PULL|PUSH)
	//用不同的方法打开目标文件(读或写)
	switch ( mode )
	{
		case DATA_PUSH :
			ptr->file_fd = open(path,O_WRONLY|O_CREAT|O_TRUNC);
			break;
		case DATA_PULL :
			ptr->file_fd = open(path,O_RDONLY);
			break;
		default :
			break;
	}

#ifdef DEBUG
	if( mode == DATA_PUSH )
	{
		cout << ptr->usr_name << "\tpush file\t" << path << endl;
	} else
	{
		cout << ptr->usr_name << "\tpull file\t" << path << endl;
	}
#endif

	if( ptr->file_fd == -1 )
	{
		cerr << "data_read_path open file " << path;
		data_error(NULL,ptr);
		return -1;
	}
	
	//设为非阻塞
	setnonblocking(ptr->file_fd);

	if(mode == DATA_PULL)		//若是从服务器拉取文件 则读取文件大小
	{
		struct stat buf;

		if( fstat(ptr->file_fd,&buf) < 0 )
		{
			data_error("data_read_path stat",ptr);
			return -1;
		}

		ptr->tsfd = 0;
		ptr->total = buf.st_size;
	}
	else						//若下载文件 则打开一个pipe 因为会调用splice
	{
		if( pipe(ptr->p) < 0 )
		{
			perror("data_read_path pipe");
			return -1;
		}
	}
	ptr->state = mode == DATA_PULL ? SENDING : RECEIVING;
	ptr->transfering = true;
	
	return 0;
}

/* ---------------------------------------------------------------*/
/**
 * 函数简介:  data进程中读取用户ID的函数 
 *
 * 参数: ptr 对方用户信息指针
 * 参数: name 用户缓冲区
 *
 * 返回值： -1表示失败 0表示成功
 */
/* ---------------------------------------------------------------*/
int data_read_id(data_sock_info_t *ptr)
{
	setblocking(ptr->sock_fd);
	int res = readn(ptr->sock_fd,ptr->usr_name,USR_LEN);
	setnonblocking(ptr->sock_fd);

	if(res == -1)
	{
		if( errno == EAGAIN || errno == EWOULDBLOCK )
		{
			ptr->state = WAIT_ID;
			return -1;
		}
		data_error("data_read_id read",ptr);
		return -1;
	}
	else if(res == 0)	//若对方关闭socket
	{
		close(ptr->sock_fd);
		delete ptr;
		return -1;
	}

	ptr->state = WAIT_PATH;

	return 0;
}

/* ---------------------------------------------------------------*/
/**
 * 函数简介: 打开日志文件 
 *
 * 参数: ptr 指针
 *
 * 返回值:   -1失败 0成功
 */
/* ---------------------------------------------------------------*/
int open_log_file(data_sock_info_t *ptr)
{
	char log[USR_LEN];
	snprintf(log,USR_LEN,"%s/%s/.log/%d.log",SRV_ROOT,
			ptr->usr_name,ptr->peer_version);
	
	if( (ptr->file_fd = open(log,O_RDONLY)) < 0 )
	{
		cerr << "open_log_file " << log;
		data_error(" error:",ptr);
		return -1;
	}
	
	//设为非阻塞
	setnonblocking(ptr->file_fd);

	struct stat buf;

	if( fstat(ptr->file_fd,&buf) < 0 )
	{
		data_error("open_log_file stat",ptr);
		return -1;
	}

	(ptr->peer_version)++;
	ptr->tsfd = 0;
	ptr->total = buf.st_size;
	
	return 0;
}


/* ---------------------------------------------------------------*/
/**
 * 函数简介:  读取版本号信息
 *
 * 参数: flag 有peer和real两种情况
 * 参数: ptr  指向用户信息的指针
 *
 * 返回值:   -1表示成功 0表示失败
 */
/* ---------------------------------------------------------------*/
int data_read_version(int flag, data_sock_info_t *ptr)
{
	if( flag == SYNC_WAIT_PEER )
	{
		if( readn(ptr->sock_fd,&ptr->peer_version,
					sizeof(ptr->peer_version)) <= 0 )
		{
			if( errno == EAGAIN || errno == EWOULDBLOCK )
			{
				ptr->state = SYNC_WAIT_PEER;
				return -1;
			}
			
			data_error("data_read_version peer",ptr);
			return -1;
		}
		ptr->peer_version++;
		return 0;
	} 
	else if( flag == SYNC_WAIT_REAL)
	{
		if( readn(ptr->sock_fd,&ptr->real_version,
					sizeof(ptr->real_version)) <= 0 )
		{
			if( errno == EAGAIN || errno == EWOULDBLOCK )
			{
				ptr->state = SYNC_WAIT_REAL;
				return -1;
			}
			
			data_error("data_read_version real",ptr);
			return -1;
		}

		//cout << ptr->real_version << endl;		
		return 0;
	}
}

/* ---------------------------------------------------------------*/
/**
 * 函数简介:  处理data_process中的错误退出
 *
 * 参数: msg  提供给perror的字符串
 * 参数: info 用户信息指针
 */
/* ---------------------------------------------------------------*/
void data_error(const char* msg, data_sock_info_t* info)
{
	perror(msg);
	close(info->sock_fd);

	if( info->transfering )
	{
		close(info->file_fd);
		if(info->type == DATA_PUSH)
		{
			close(info->p[0]);
			close(info->p[1]);
		}
	}

	delete info;
}

/* ---------------------------------------------------------------*/
/**
 * 函数简介:  处理用户退出(包括正常与非正常)
 *			  当用户登录期间有更新的时候，就
 *			  更新版本号，并且保存日志。
 *
 * 参数: ptr  用户信息
 */
/* ---------------------------------------------------------------*/
void usr_exit(ctl_sock_info_t* info)
{
	if( !info )
		return;
	
	close(info->fd);
	close(info->notify_fd);

	if( strlen(info->usr_name) > 0 )
	{
#ifdef DEBUG
		cout << info->usr_name << "\texit" << endl;
#endif
		//如果登录期间发生了更新
		//则把更新写入日志
		if( !info->history.empty() )
		{
			info->version++;
			
			char version_file[PATH_LEN];
			snprintf(version_file,PATH_LEN,"%s/%s/.version",
					SRV_ROOT,info->usr_name);

			//打开version file
			int vf_fd = open(version_file,O_WRONLY | O_TRUNC);
			if( vf_fd < 0 )
			{
				cerr << "usr_exit -> open version file: " << version_file; 
				perror(NULL);
				return;
			}

			//写入版本号
			if( write(vf_fd,&(info->version),sizeof(info->version)) < 0 )
			{
				perror("write to version file");
				return;
			}

			close(vf_fd);

			//生成日志
			char log[PATH_LEN];
			snprintf(log,PATH_LEN,"%s/%s/.log/%d.log",SRV_ROOT,
						info->usr_name, info->version);

			int log_fd = open(log, O_WRONLY | O_CREAT, 0766);
			if( log_fd < 0 )
			{
				cerr << "usr_exit -> open log file: " << log; 
				perror(NULL);
				return;
			}

			for( int i = 0; i < info->history.size() ; i++ )
			{
				if( write(log_fd,info->history[i],sizeof(ctl_pkg_t)) < 0 )
				{
					perror("usr_exit -> write to log");
					return;
				}
				delete info->history[i];
			}

			close(log_fd);
		}
	}
	
	delete info;
	online_info->erase(info->usr_name);

}


/* ---------------------------------------------------------------*/
/**
 * 函数简介:   递归删除目录
 *
 * 参数: path  要删除的目录
 *
 * 返回值:     -1表示失败 0表示成功
 */
/* ---------------------------------------------------------------*/
int dfs_rmdir(const char *path)
{
	struct stat info;
	if( stat(path,&info) < 0 )
	{
		perror("stat");
		return -1;
	}

	
	if( S_ISDIR(info.st_mode) )	//如果是目录  则递归删除
	{
		DIR *pre = opendir(path);
		if( pre == NULL  )
		{
			perror("opendir");
			return -1;
		}
		
		struct dirent *entry = readdir(pre);
		while(entry != NULL)
		{
			if( strcmp(entry->d_name,".") == 0 || 
					strcmp(entry->d_name,"..") == 0 )
			{
				entry = readdir(pre);
				continue;
			}
			
			string temp(path);
			temp += "/";
			temp += entry->d_name;

			if( stat(temp.c_str(),&info) )
			{
				perror("stat");
				return -1;
			}

			if(S_ISDIR(info.st_mode) )
			{
				dfs_rmdir(temp.c_str());
			} 
			else
			{
				unlink(temp.c_str());
			}

			entry = readdir(pre);
		}
		closedir(pre);
		rmdir(path);
		return 0;

	}
	else			//如果是文件  直接删除
	{
		return unlink(path);
	}
}


/* ---------------------------------------------------------------*/
/**
 * 函数简介:  获取目录中的所有项的信息，存入队列中
 *
 * 参数: pkg	客户端的请求包
 * 参数: info	用户信息指针
 */
/* ---------------------------------------------------------------*/
void get_list(ctl_pkg_t *pkg, ctl_sock_info_t *info)
{
	DIR *dir = opendir(pkg->str.path);
	if( dir == NULL )
	{
		cout << pkg->str.path << endl;
		perror("opendir");
		return;
	}
	struct dirent *dir_info = readdir(dir);
	struct stat file_info;
		
	while( dir_info != NULL )
	{
		if( strcmp(dir_info->d_name,".") == 0 
				|| strcmp(dir_info->d_name,"..") == 0 
				|| strcmp(dir_info->d_name,".version") == 0 
				|| strcmp(dir_info->d_name,".log") == 0 )
		{
				dir_info = readdir(dir);
				continue;
		}
		char path[PATH_LEN];
		sprintf(path,"%s/%s",pkg->str.path,dir_info->d_name);

		if( stat(path,&file_info) < 0 )
		{
				perror("stat");
				dir_info = readdir(dir);
				continue;
		}

		item_info_t *item_info_p = new item_info_t;

		if( S_ISDIR(file_info.st_mode) )
			item_info_p->type = 2;	
		else
			item_info_p->type = 1;
		
		strncpy(item_info_p->name,dir_info->d_name,FILE_LEN);
		info->items.push_back(item_info_p);
		
		dir_info = readdir(dir);
	}

	item_info_t	*item_info_p = new item_info_t;
	item_info_p->type = 0;		//哨兵
	info->items.push_back(item_info_p);

}
